/*
 * Copyright (c) 2008 INRIA
 *
 * SPDX-License-Identifier: GPL-2.0-only
 *
 * Authors: Mathieu Lacage <mathieu.lacage@sophia.inria.fr>
 */
#include "ns3/callback.h"
#include "ns3/config.h"
#include "ns3/integer.h"
#include "ns3/log.h"
#include "ns3/names.h"
#include "ns3/object-vector.h"
#include "ns3/object.h"
#include "ns3/pointer.h"
#include "ns3/singleton.h"
#include "ns3/test.h"
#include "ns3/trace-source-accessor.h"
#include "ns3/traced-value.h"

#include <sstream>

/**
 * @file
 * @ingroup core-tests
 * @ingroup config
 * @ingroup config-tests
 * Config test suite
 */

/**
 * @ingroup core-tests
 * @defgroup config-tests Config test suite
 */

namespace ns3
{

namespace tests
{

/**
 * @ingroup config-tests
 * An object with some attributes that we can play with using config.
 */
class ConfigTestObject : public Object
{
  public:
    /**
     * @brief Get the type ID.
     * @return the object TypeId
     */
    static TypeId GetTypeId();

    /**
     * Add node A function
     * @param a test object a
     */
    void AddNodeA(Ptr<ConfigTestObject> a);
    /**
     * Add node B function
     * @param b test object b
     */
    void AddNodeB(Ptr<ConfigTestObject> b);

    /**
     * Set node A function
     * @param a test object a
     */
    void SetNodeA(Ptr<ConfigTestObject> a);
    /**
     * Set node b function
     * @param b test object b
     */
    void SetNodeB(Ptr<ConfigTestObject> b);

    /**
     * Get node A function
     * @returns the value of node a
     */
    int8_t GetA() const;
    /**
     * Get node b function
     * @returns the value of node b
     */
    int8_t GetB() const;

  private:
    std::vector<Ptr<ConfigTestObject>> m_nodesA; //!< NodesA attribute target.
    std::vector<Ptr<ConfigTestObject>> m_nodesB; //!< NodesB attribute target.
    Ptr<ConfigTestObject> m_nodeA;               //!< NodeA attribute target.
    Ptr<ConfigTestObject> m_nodeB;               //!< NodeB attribute target.
    int8_t m_a;                                  //!< A attribute target.
    int8_t m_b;                                  //!< B attribute target.
    TracedValue<int16_t> m_trace;                //!< Source TraceSource target.
};

TypeId
ConfigTestObject::GetTypeId()
{
    static TypeId tid = TypeId("ConfigTestObject")
                            .SetParent<Object>()
                            .AddAttribute("NodesA",
                                          "",
                                          ObjectVectorValue(),
                                          MakeObjectVectorAccessor(&ConfigTestObject::m_nodesA),
                                          MakeObjectVectorChecker<ConfigTestObject>())
                            .AddAttribute("NodesB",
                                          "",
                                          ObjectVectorValue(),
                                          MakeObjectVectorAccessor(&ConfigTestObject::m_nodesB),
                                          MakeObjectVectorChecker<ConfigTestObject>())
                            .AddAttribute("NodeA",
                                          "",
                                          PointerValue(),
                                          MakePointerAccessor(&ConfigTestObject::m_nodeA),
                                          MakePointerChecker<ConfigTestObject>())
                            .AddAttribute("NodeB",
                                          "",
                                          PointerValue(),
                                          MakePointerAccessor(&ConfigTestObject::m_nodeB),
                                          MakePointerChecker<ConfigTestObject>())
                            .AddAttribute("A",
                                          "",
                                          IntegerValue(10),
                                          MakeIntegerAccessor(&ConfigTestObject::m_a),
                                          MakeIntegerChecker<int8_t>())
                            .AddAttribute("B",
                                          "",
                                          IntegerValue(9),
                                          MakeIntegerAccessor(&ConfigTestObject::m_b),
                                          MakeIntegerChecker<int8_t>())
                            .AddAttribute("Source",
                                          "XX",
                                          IntegerValue(-1),
                                          MakeIntegerAccessor(&ConfigTestObject::m_trace),
                                          MakeIntegerChecker<int16_t>())
                            .AddTraceSource("Source",
                                            "XX",
                                            MakeTraceSourceAccessor(&ConfigTestObject::m_trace),
                                            "ns3::TracedValueCallback::Int16");
    return tid;
}

void
ConfigTestObject::SetNodeA(Ptr<ConfigTestObject> a)
{
    m_nodeA = a;
}

void
ConfigTestObject::SetNodeB(Ptr<ConfigTestObject> b)
{
    m_nodeB = b;
}

void
ConfigTestObject::AddNodeA(Ptr<ConfigTestObject> a)
{
    m_nodesA.push_back(a);
}

void
ConfigTestObject::AddNodeB(Ptr<ConfigTestObject> b)
{
    m_nodesB.push_back(b);
}

int8_t
ConfigTestObject::GetA() const
{
    return m_a;
}

int8_t
ConfigTestObject::GetB() const
{
    return m_b;
}

/**
 * @ingroup config-tests
 * Derived test objects.
 */
class DerivedConfigTestObject : public ConfigTestObject
{
  public:
    /**
     * @brief Get the type ID.
     * @return the object TypeId
     */
    static TypeId GetTypeId();

    /** Constructor. */
    DerivedConfigTestObject()
    {
    }

    /** Destructor */
    ~DerivedConfigTestObject() override
    {
    }
};

TypeId
DerivedConfigTestObject::GetTypeId()
{
    static TypeId tid = TypeId("DerivedConfigTestObject").SetParent<ConfigTestObject>();
    return tid;
}

/**
 * @ingroup config-tests
 * Base config object.
 */
class BaseConfigObject : public Object
{
  public:
    /**
     * @brief Get the type ID.
     * @return the object TypeId
     */
    static TypeId GetTypeId();

    /** Constructor. */
    BaseConfigObject()
        : m_x(15)
    {
    }

    /** Destructor. */
    ~BaseConfigObject() override
    {
    }

  private:
    int8_t m_x; //!< X attribute target.
};

TypeId
BaseConfigObject::GetTypeId()
{
    static TypeId tid = TypeId("BaseConfigObject")
                            .SetParent<Object>()
                            .AddAttribute("X",
                                          "",
                                          IntegerValue(10),
                                          MakeIntegerAccessor(&BaseConfigObject::m_x),
                                          MakeIntegerChecker<int8_t>());
    return tid;
}

/**
 * @ingroup config-tests
 * Derived config object.
 */
class DerivedConfigObject : public BaseConfigObject
{
  public:
    /**
     * @brief Get the type ID.
     * @return the object TypeId
     */
    static TypeId GetTypeId();

    /** Constructor. */
    DerivedConfigObject()
    {
    }

    /** Destructor. */
    ~DerivedConfigObject() override
    {
    }
};

TypeId
DerivedConfigObject::GetTypeId()
{
    static TypeId tid = TypeId("DerivedConfigObject").SetParent<BaseConfigObject>();
    return tid;
}

/**
 * @ingroup config-tests
 * Test for the ability to register and use a root namespace.
 */
class RootNamespaceConfigTestCase : public TestCase
{
  public:
    /** Constructor. */
    RootNamespaceConfigTestCase();

    /** Destructor. */
    ~RootNamespaceConfigTestCase() override
    {
    }

  private:
    void DoRun() override;
};

RootNamespaceConfigTestCase::RootNamespaceConfigTestCase()
    : TestCase("Check ability to register a root namespace and use it")
{
}

void
RootNamespaceConfigTestCase::DoRun()
{
    IntegerValue iv;
    //
    // Create an object and register its attributes directly in the root
    // namespace.
    //
    Ptr<ConfigTestObject> root = CreateObject<ConfigTestObject>();
    Config::RegisterRootNamespaceObject(root);

    //
    // We should find the default values there.
    //
    root->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), 10, "Object Attribute \"A\" not initialized as expected");

    //
    // Now use the config mechanism to set the attribute; and we should find the
    // new value.
    //
    Config::Set("/A", IntegerValue(1));
    root->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), 1, "Object Attribute \"A\" not set correctly");

    //
    // We should find the default values of "B" too.
    //
    root->GetAttribute("B", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), 9, "Object Attribute \"B\" not initialized as expected");

    //
    // Now use the config mechanism to set the attribute; and we should find the
    // new value.
    //
    Config::Set("/B", IntegerValue(-1));
    root->GetAttribute("B", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), -1, "Object Attribute \"B\" not set correctly");
}

/**
 * @ingroup config-tests
 * Test for the ability to add an object under the root namespace.
 */
class UnderRootNamespaceConfigTestCase : public TestCase
{
  public:
    /** Constructor. */
    UnderRootNamespaceConfigTestCase();

    /** Destructor. */
    ~UnderRootNamespaceConfigTestCase() override
    {
    }

  private:
    void DoRun() override;
};

UnderRootNamespaceConfigTestCase::UnderRootNamespaceConfigTestCase()
    : TestCase("Check ability to register an object under the root namespace and use it")
{
}

void
UnderRootNamespaceConfigTestCase::DoRun()
{
    IntegerValue iv;
    //
    // Create an object and register its attributes directly in the root
    // namespace.
    //
    Ptr<ConfigTestObject> root = CreateObject<ConfigTestObject>();
    Config::RegisterRootNamespaceObject(root);

    Ptr<ConfigTestObject> a = CreateObject<ConfigTestObject>();
    root->SetNodeA(a);

    //
    // We should find the default values there.
    //
    a->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), 10, "Object Attribute \"A\" not initialized as expected");

    //
    // Now use the config mechanism to set the attribute; and we should find the
    // new value.
    //
    Config::Set("/NodeA/A", IntegerValue(1));
    a->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), 1, "Object Attribute \"A\" not set correctly");

    //
    // We should find the default values of "B" too.
    //
    a->GetAttribute("B", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), 9, "Object Attribute \"B\" not initialized as expected");

    //
    // Now use the config mechanism to set the attribute; and we should find the
    // new value.
    //
    Config::Set("/NodeA/B", IntegerValue(-1));
    a->GetAttribute("B", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), -1, "Object Attribute \"B\" not set correctly");

    //
    // Try and set through a nonexistent path.  Should do nothing.
    //
    Config::Set("/NodeB/A", IntegerValue(1234));
    a->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), 1, "Object Attribute \"A\" unexpectedly set via bad path");

    Config::Set("/NodeB/B", IntegerValue(1234));
    a->GetAttribute("B", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), -1, "Object Attribute \"B\" unexpectedly set via bad path");

    //
    // Step down one level of recursion and try again
    //
    Ptr<ConfigTestObject> b = CreateObject<ConfigTestObject>();

    //
    // We should find the default values there.
    //
    b->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), 10, "Object Attribute \"A\" not initialized as expected");
    b->GetAttribute("B", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), 9, "Object Attribute \"B\" not initialized as expected");

    //
    // Now tell A that it has a B; and we should be able to set this new object's
    // Attributes.
    //
    a->SetNodeB(b);

    Config::Set("/NodeA/NodeB/A", IntegerValue(4));
    Config::Set("/NodeA/NodeB/B", IntegerValue(-4));
    b->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), 4, "Object Attribute \"A\" not set as expected");
    b->GetAttribute("B", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), -4, "Object Attribute \"B\" not set as expected");

    //
    // Try '*' for attributes
    //
    Config::Set("/*/A", IntegerValue(2));
    a->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), 2, "Object Attribute \"A\" not set correctly");
    b->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), 4, "Object Attribute \"A\" not set correctly");
}

/**
 * @ingroup config-tests
 * Test for the ability to deal configure with vectors of objects.
 */
class ObjectVectorConfigTestCase : public TestCase
{
  public:
    /** Constructor. */
    ObjectVectorConfigTestCase();

    /** Destructor. */
    ~ObjectVectorConfigTestCase() override
    {
    }

  private:
    void DoRun() override;
};

ObjectVectorConfigTestCase::ObjectVectorConfigTestCase()
    : TestCase("Check ability to configure vectors of Object using regular expressions")
{
}

void
ObjectVectorConfigTestCase::DoRun()
{
    IntegerValue iv;

    //
    // Create a root namespace object
    //
    Ptr<ConfigTestObject> root = CreateObject<ConfigTestObject>();
    Config::RegisterRootNamespaceObject(root);

    //
    // Create an object under the root.
    //
    Ptr<ConfigTestObject> a = CreateObject<ConfigTestObject>();
    root->SetNodeA(a);

    //
    // Create an object one level down.
    //
    Ptr<ConfigTestObject> b = CreateObject<ConfigTestObject>();
    a->SetNodeB(b);

    //
    // Add four objects to the ObjectVector Attribute at the bottom of the
    // object hierarchy.  By this point, we believe that the Attributes
    // will be initialized correctly.
    //
    Ptr<ConfigTestObject> obj0 = CreateObject<ConfigTestObject>();
    Ptr<ConfigTestObject> obj1 = CreateObject<ConfigTestObject>();
    Ptr<ConfigTestObject> obj2 = CreateObject<ConfigTestObject>();
    Ptr<ConfigTestObject> obj3 = CreateObject<ConfigTestObject>();
    b->AddNodeB(obj0);
    b->AddNodeB(obj1);
    b->AddNodeB(obj2);
    b->AddNodeB(obj3);

    //
    // Set an Attribute of the zeroth Object in the vector by explicitly writing
    // the '0' and make sure that only the one thing changed.
    //
    Config::Set("/NodeA/NodeB/NodesB/0/A", IntegerValue(-11));
    obj0->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), -11, "Object Attribute \"A\" not set as expected");

    obj1->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), 10, "Object Attribute \"A\" unexpectedly set");

    obj2->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), 10, "Object Attribute \"A\" unexpectedly set");

    obj3->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), 10, "Object Attribute \"A\" unexpectedly set");

    //
    // Start using regular expression-like syntax to set Attributes.  First try
    // the OR syntax.  Make sure that the two objects changed and nothing else
    //
    Config::Set("/NodeA/NodeB/NodesB/0|1/A", IntegerValue(-12));
    obj0->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), -12, "Object Attribute \"A\" not set as expected");

    obj1->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), -12, "Object Attribute \"A\" not set as expected");

    obj2->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), 10, "Object Attribute \"A\" unexpectedly set");

    obj3->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), 10, "Object Attribute \"A\" unexpectedly set");

    //
    // Make sure that extra '|' are allowed at the start and end of the regular expression
    //
    Config::Set("/NodeA/NodeB/NodesB/|0|1|/A", IntegerValue(-13));
    obj0->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), -13, "Object Attribute \"A\" not set as expected");

    obj1->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), -13, "Object Attribute \"A\" not set as expected");

    obj2->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), 10, "Object Attribute \"A\" unexpectedly set");

    obj3->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), 10, "Object Attribute \"A\" unexpectedly set");

    //
    // Try the [x-y] syntax
    //
    Config::Set("/NodeA/NodeB/NodesB/[0-2]/A", IntegerValue(-14));
    obj0->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), -14, "Object Attribute \"A\" not set as expected");

    obj1->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), -14, "Object Attribute \"A\" not set as expected");

    obj2->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), -14, "Object Attribute \"A\" not set as expected");

    obj3->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), 10, "Object Attribute \"A\" unexpectedly set");

    //
    // Try the [x-y] syntax at the other limit
    //
    Config::Set("/NodeA/NodeB/NodesB/[1-3]/A", IntegerValue(-15));
    obj0->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), -14, "Object Attribute \"A\" unexpectedly set");

    obj1->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), -15, "Object Attribute \"A\" not set as expected");

    obj2->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), -15, "Object Attribute \"A\" not set as expected");

    obj3->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), -15, "Object Attribute \"A\" not set as expected");

    //
    // Combine the [x-y] syntax and the OR sntax
    //
    Config::Set("/NodeA/NodeB/NodesB/[0-1]|3/A", IntegerValue(-16));
    obj0->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), -16, "Object Attribute \"A\" not set as expected");

    obj1->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), -16, "Object Attribute \"A\" not set as expected");

    obj2->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), -15, "Object Attribute \"A\" unexpectedly set");

    obj3->GetAttribute("A", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), -16, "Object Attribute \"A\" not set as expected");
}

/**
 * @ingroup config-tests
 * Test for the ability to trace configure with vectors of objects.
 */
class ObjectVectorTraceConfigTestCase : public TestCase
{
  public:
    /** Constructor. */
    ObjectVectorTraceConfigTestCase();

    /** Destructor. */
    ~ObjectVectorTraceConfigTestCase() override
    {
    }

    /**
     * Trace callback without context.
     * @param oldValue The old value.
     * @param newValue The new value.
     */
    void Trace(int16_t oldValue [[maybe_unused]], int16_t newValue)
    {
        m_newValue = newValue;
    }

    /**
     * Trace callback with context path.
     * @param path The context path.
     * @param old The old value.
     * @param newValue The new value.
     */
    void TraceWithPath(std::string path, int16_t old [[maybe_unused]], int16_t newValue)
    {
        m_newValue = newValue;
        m_path = path;
    }

  private:
    void DoRun() override;

    int16_t m_newValue; //!< Flag to detect tracing result.
    std::string m_path; //!< The context path.
};

ObjectVectorTraceConfigTestCase::ObjectVectorTraceConfigTestCase()
    : TestCase("Check ability to trace connect through vectors of Object using regular expressions")
{
}

void
ObjectVectorTraceConfigTestCase::DoRun()
{
    IntegerValue iv;

    //
    // Create a root namespace object
    //
    Ptr<ConfigTestObject> root = CreateObject<ConfigTestObject>();
    Config::RegisterRootNamespaceObject(root);

    //
    // Create an object under the root.
    //
    Ptr<ConfigTestObject> a = CreateObject<ConfigTestObject>();
    root->SetNodeA(a);

    //
    // Create an object one level down.
    //
    Ptr<ConfigTestObject> b = CreateObject<ConfigTestObject>();
    a->SetNodeB(b);

    //
    // Add four objects to the ObjectVector Attribute at the bottom of the
    // object hierarchy.  By this point, we believe that the Attributes
    // will be initialized correctly.
    //
    Ptr<ConfigTestObject> obj0 = CreateObject<ConfigTestObject>();
    Ptr<ConfigTestObject> obj1 = CreateObject<ConfigTestObject>();
    Ptr<ConfigTestObject> obj2 = CreateObject<ConfigTestObject>();
    Ptr<ConfigTestObject> obj3 = CreateObject<ConfigTestObject>();
    b->AddNodeB(obj0);
    b->AddNodeB(obj1);
    b->AddNodeB(obj2);
    b->AddNodeB(obj3);

    //
    // Do a trace connect to some of the sources.  We already checked parsing of
    // the regular expressions, so we'll concentrate on the tracing part of the
    // puzzle here.
    //
    Config::ConnectWithoutContext("/NodeA/NodeB/NodesB/[0-1]|3/Source",
                                  MakeCallback(&ObjectVectorTraceConfigTestCase::Trace, this));

    //
    // If we bug the trace source referred to by index '0' above, we should see
    // the trace fire.
    //
    m_newValue = 0;
    obj0->SetAttribute("Source", IntegerValue(-1));
    NS_TEST_ASSERT_MSG_EQ(m_newValue, -1, "Trace 0 did not fire as expected");

    //
    // If we bug the trace source referred to by index '1' above, we should see
    // the trace fire.
    //
    m_newValue = 0;
    obj1->SetAttribute("Source", IntegerValue(-2));
    NS_TEST_ASSERT_MSG_EQ(m_newValue, -2, "Trace 1 did not fire as expected");

    //
    // If we bug the trace source referred to by index '2' which is skipped above,
    // we should not see the trace fire.
    //
    m_newValue = 0;
    obj2->SetAttribute("Source", IntegerValue(-3));
    NS_TEST_ASSERT_MSG_EQ(m_newValue, 0, "Trace 2 fired unexpectedly");

    //
    // If we bug the trace source referred to by index '3' above, we should see
    // the trace fire.
    //
    m_newValue = 0;
    obj3->SetAttribute("Source", IntegerValue(-4));
    NS_TEST_ASSERT_MSG_EQ(m_newValue, -4, "Trace 3 did not fire as expected");

    //
    // Do a trace connect (with context) to some of the sources.
    //
    Config::Connect("/NodeA/NodeB/NodesB/[0-1]|3/Source",
                    MakeCallback(&ObjectVectorTraceConfigTestCase::TraceWithPath, this));

    //
    // If we bug the trace source referred to by index '0' above, we should see
    // the trace fire with the expected context path.
    //
    m_newValue = 0;
    m_path = "";
    obj0->SetAttribute("Source", IntegerValue(-1));
    NS_TEST_ASSERT_MSG_EQ(m_newValue, -1, "Trace 0 did not fire as expected");
    NS_TEST_ASSERT_MSG_EQ(m_path,
                          "/NodeA/NodeB/NodesB/0/Source",
                          "Trace 0 did not provide expected context");

    //
    // If we bug the trace source referred to by index '1' above, we should see
    // the trace fire with the expected context path.
    //
    m_newValue = 0;
    m_path = "";
    obj1->SetAttribute("Source", IntegerValue(-2));
    NS_TEST_ASSERT_MSG_EQ(m_newValue, -2, "Trace 1 did not fire as expected");
    NS_TEST_ASSERT_MSG_EQ(m_path,
                          "/NodeA/NodeB/NodesB/1/Source",
                          "Trace 1 did not provide expected context");

    //
    // If we bug the trace source referred to by index '2' which is skipped above,
    // we should not see the trace fire.
    //
    m_newValue = 0;
    m_path = "";
    obj2->SetAttribute("Source", IntegerValue(-3));
    NS_TEST_ASSERT_MSG_EQ(m_newValue, 0, "Trace 2 fired unexpectedly");

    //
    // If we bug the trace source referred to by index '3' above, we should see
    // the trace fire with the expected context path.
    //
    m_newValue = 0;
    m_path = "";
    obj3->SetAttribute("Source", IntegerValue(-4));
    NS_TEST_ASSERT_MSG_EQ(m_newValue, -4, "Trace 3 did not fire as expected");
    NS_TEST_ASSERT_MSG_EQ(m_path,
                          "/NodeA/NodeB/NodesB/1/Source",
                          "Trace 1 did not provide expected context");
}

/**
 * @ingroup config-tests
 * Test for the ability to search attributes of parent classes
 * when Resolver searches for attributes in a derived class object.
 * This test passes with the patch found in
 * https://www.nsnam.org/bugzilla/show_bug.cgi?id=1673
 * (also reported in https://www.nsnam.org/bugzilla/show_bug.cgi?id=1959)
 */
class SearchAttributesOfParentObjectsTestCase : public TestCase
{
  public:
    /** Constructor. */
    SearchAttributesOfParentObjectsTestCase();

    /** Destructor. */
    ~SearchAttributesOfParentObjectsTestCase() override
    {
    }

  private:
    void DoRun() override;
};

SearchAttributesOfParentObjectsTestCase::SearchAttributesOfParentObjectsTestCase()
    : TestCase("Check that attributes of base class are searchable from paths including objects of "
               "derived class")
{
}

void
SearchAttributesOfParentObjectsTestCase::DoRun()
{
    IntegerValue iv;
    //
    // Create a root namespace object that doesn't have attributes but
    // whose parent class has 'NodeA' attribute
    //
    Ptr<DerivedConfigTestObject> root = CreateObject<DerivedConfigTestObject>();
    Config::RegisterRootNamespaceObject(root);

    //
    //  Instantiate /NodeA
    //
    Ptr<DerivedConfigTestObject> a = CreateObject<DerivedConfigTestObject>();
    root->SetNodeA(a);

    //
    // BaseConfigObject has attribute X, but we aggregate DerivedConfigObject
    // instead
    //
    Ptr<DerivedConfigObject> derived = CreateObject<DerivedConfigObject>();
    a->AggregateObject(derived);
    Config::Set("/NodeA/$DerivedConfigObject/X", IntegerValue(42));
    derived->GetAttribute("X", iv);
    NS_TEST_ASSERT_MSG_EQ(iv.Get(), 42, "Object Attribute \"X\" not settable in derived class");
}

/**
 * @ingroup config-tests
 * The Test Suite that glues all of the Test Cases together.
 */
class ConfigTestSuite : public TestSuite
{
  public:
    /** Constructor. */
    ConfigTestSuite();
};

ConfigTestSuite::ConfigTestSuite()
    : TestSuite("config")
{
    AddTestCase(new RootNamespaceConfigTestCase);
    AddTestCase(new UnderRootNamespaceConfigTestCase);
    AddTestCase(new ObjectVectorConfigTestCase);
    AddTestCase(new SearchAttributesOfParentObjectsTestCase);
}

/**
 * @ingroup config-tests
 * ConfigTestSuite instance variable.
 */
static ConfigTestSuite g_configTestSuite;

} // namespace tests

} // namespace ns3
